﻿using System;
using weka.core;
using weka.classifiers;
using weka.filters;
using weka.filters.unsupervised.attribute;

namespace OMC.Classification
{
    /// <summary>
    /// an object of this class contain all about Text Classification
    /// with Weka. The imported assemblies of WEKA are generated with IKVM
    /// </summary>
    /// <remarks>
    /// Copyright 2012 Stefan Lütolf
    /// Source: http://stackoverflow.com/questions/9707825/basic-text-classification-with-weka-in-java
    /// </remarks>
    public class TextClassifier
    {
        private Instances TheTrainingData;
        private Instances TheFilteredTrainingData;
        private Instance TheTestData;
        private Instance TheFilteredTestData;
        private Instances TheTestSet;
        private FastVector TheClassValues;
        private FastVector TheAttributes;
        private StringToWordVector TheFilter;
        private Classifier TheClassifier;
        private bool ClassesAreBuilt;

        /// <summary>
        /// constructor of a Textclassifier which starts another constructor
        /// </summary>
        /// <param name="inClassifier">a Classifier such as Naive Bayes..</param>
        public TextClassifier(Classifier inClassifier)
            : this(inClassifier, 10)
        {
        }

        /// <summary>
        /// a constructor of a TextClassifier
        /// </summary>
        /// <param name="inClassifier">a Classifier such as Naive Bayes..</param>
        /// <param name="inStartSize">Start size of the FastVector</param>
        private TextClassifier(Classifier inClassifier, int inStartSize)
        {
            this.ClassesAreBuilt = false;
            this.TheFilter = new StringToWordVector();
            this.TheClassifier = inClassifier;
            // Create vector of attributes.
            this.TheAttributes = new FastVector(2);
            // Add attribute for holding texts.
            this.TheAttributes.addElement(new weka.core.Attribute("text", (FastVector)null));
            // Add class attribute.
            this.TheClassValues = new FastVector(inStartSize);
            this.TheTrainingData = null;
            this.TheFilteredTrainingData = null;
            this.TheTestSet = null;

        }

        /// <summary>
        /// to set the Trainings Dataset
        /// </summary>
        /// <param name="inData">the TrainingDataset</param>
        public void setTrainingDataset(Instances inData)
        {
            this.TheTrainingData = inData;
        }

        /// <summary>
        /// to catch the Class Name of a Class Index
        /// </summary>
        /// <param name="index">the index of a ClassName</param>
        /// <returns>a String of the ClassName</returns>
        public String getCategory(int index)
        {
            return TheFilteredTestData.classAttribute().value(index);
        }

        /// <summary>
        /// to add classes of classification to the instances
        /// </summary>
        /// <param name="inCategory">the Classname of a class</param>
        public void addClass(String inCategory)
        {
            //category = category.ToLower();
            // if required, double the capacity.
            int capacity = TheClassValues.capacity();
            if (TheClassValues.size() > (capacity - 5))
            {
                TheClassValues.setCapacity(capacity * 2);
            }
            TheClassValues.addElement(inCategory);
        }

        /// <summary>
        /// to build the classes of the classification
        /// </summary>
        public void buildAfterClassesAdded()
        {
            TheAttributes.addElement(new weka.core.Attribute("class", TheClassValues));
            // Create dataset with initial capacity of 100, and set index of class.
            TheTrainingData = new Instances("BA_Classification", TheAttributes, 100);
            TheTrainingData.setClassIndex(TheTrainingData.numAttributes() - 1);
            ClassesAreBuilt = true;
        }

        /// <summary>
        /// to add data to the instances
        /// </summary>
        /// <param name="inMailContent">the mailcontent to add</param>
        /// <param name="inClassName">in which classname the mailcontent is</param>
        public void addMailData(String inMailContent, String inClassName)
        {
            if (!ClassesAreBuilt)
            {
                throw new Exception("Must use buildAfterClassesAdded first");
            }
            inMailContent = inMailContent.ToLower();
            //className = className.ToLower();
            // Make message into instance.
            Instance instance = makeInstance(inMailContent, TheTrainingData);
            // Set class value for instance.
            instance.setClassValue(inClassName);
            // Add instance to training data.
            TheTrainingData.add(instance);
        }

        /// <summary>
        /// to build if the MailData are added
        /// </summary>
        /// <returns>the Instances of the builded DataSet</returns>
        public Instances buildAfterMailDataAdded()
        {
            return TheTrainingData;
        }

        /// <summary>
        /// to classify a message
        /// </summary>
        /// <param name="message">the message to classify, type of String</param>
        /// <returns>The Result in form of a double array</returns>
        public double[] classifyMessage(String message)
        {
            message = message.ToLower();

            // Check whether classifier has been built.
            if (TheTrainingData.numInstances() == 0)
            {
                throw new Exception("No classifier available.");
            }

            //Use Filter for Data
            // Initialize filter and tell it about the input format.
            TheFilter.setInputFormat(TheTrainingData);
            // Generate word counts from the training data.
            TheFilteredTrainingData = Filter.useFilter(TheTrainingData, TheFilter);
            // Rebuild classifier.
            TheClassifier.buildClassifier(TheFilteredTrainingData);

            TheTestSet = TheTrainingData.stringFreeStructure();
            TheTestData = makeInstance(message, TheTestSet);

            // Filter
            TheFilter.input(TheTestData);
            TheFilteredTestData = TheFilter.output();
            //String ClassName = filteredTestData.classAttribute().value((int)classifier.classifyInstance(filteredTestData));
            //Utils.Debug.print("Klassenname: "+ClassName);
            return TheClassifier.distributionForInstance(TheFilteredTestData);
        }

        /// <summary>
        /// to make the Instance
        /// </summary>
        /// <param name="inTextRelation">the Arff Data Relation Text</param>
        /// <param name="inData">The Data to generate the Instance</param>
        /// <returns></returns>
        private Instance makeInstance(String inTextRelation, Instances inData)
        {
            // Create instance of length two.
            Instance instance = new Instance(2);
            // Set value for message attribute
            weka.core.Attribute messageAtt = inData.attribute("text");
            instance.setValue(messageAtt, messageAtt.addStringValue(inTextRelation));
            // Give instance access to attribute information from the dataset.
            instance.setDataset(inData);
            return instance;
        }
    }
}